//
// Created by Pulsar on 2021/1/8.
//
#include <hkvqcore/driver/hkv_driver.h>
#include <hkvqcore/data_struct/data_struct.h>
#include<vector>

#define MaxCameraNum 20
namespace hkvqcore {
    namespace driver {
        static HANDLE cEvent[MaxCameraNum];
        static long nPort[MaxCameraNum];
        static IplImage *pImg[MaxCameraNum];
        static const float Scalefactor = 0.5f;
        static bool IS_INIT_SDK = false;
        static std::vector<long> Users;
        static std::vector<data_struct::CamHandle> lRealPlayHandles;

        std::mutex sdk_init;
        std::mutex users;
        std::mutex handels;
        std::mutex read_frame;
        std::condition_variable cond_frame;


        HKCamDriver::HKCamDriver() = default;

        HKCamDriver::HKCamDriver(const std::string &sIP, const std::string &UsrName, const std::string &PsW, int Port,
                                 int channel, int stream_type)
                : sIP(sIP), UsrName(UsrName), PsW(PsW), Port(Port), channel(channel), stream_type(stream_type) {
        }

        HKCamDriver::~HKCamDriver() = default;

        int HKCamDriver::ReleaseCamera() {
            std::unique_lock<std::mutex> handels_locker(handels);
            for (int i = 0; i < lRealPlayHandles.size(); i += 1) {
                if (!NET_DVR_StopRealPlay(lRealPlayHandles[i])) {
                    std::cout << "NET_DVR_StopRealPlay error! Error number: " << NET_DVR_GetLastError() << std::endl;
                }
            }
            handels_locker.unlock();
            std::unique_lock<std::mutex> users_locker(users);
            for (int i = 0; i < Users.size(); i += 1) {
                if (NET_DVR_Logout(Users[i])) {
                    std::cout << "NET_DVR_Logout error! Error number: " << NET_DVR_GetLastError() << std::endl;
                }
            }
            users_locker.unlock();
            NET_DVR_Cleanup();
//            for (int i = 0; i < MaxCameraNum; i++) {
//                CloseHandle(cEvent[i]);
//            }
            return 1;
        }

        void HKCamDriver::InitHKNetSDK() {
            std::unique_lock<std::mutex> locker(sdk_init);
            if (IS_INIT_SDK)return;
            NET_DVR_Init();
            NET_DVR_SetConnectTime(200, 1);
            NET_DVR_SetReconnect(10000, true);

            for (int i = 0; i < MaxCameraNum; i++) {
                cEvent[i] = CreateEvent(NULL, TRUE, TRUE, NULL);//CreateEvent
                nPort[i] = -1;
                pImg[i] = NULL;
            }
            IS_INIT_SDK = true;
            locker.unlock();
        }

        data_struct::CamHandle
        HKCamDriver::InitCamera(const char *sIP, const char *UsrName, const char *PsW, int Port, int channel,
                                int stream_type) {

            std::cout << "IP:" << sIP << " UserName:" << UsrName << " Password:" << PsW << " Port:" << Port
                      << " channel:" << channel
                      << " stream_type:" << stream_type
                      << std::endl;

            NET_DVR_DEVICEINFO_V30 struDeviceInfo;
            long lUserID = NET_DVR_Login_V30((char *) sIP, Port, (char *) UsrName, (char *) PsW, &struDeviceInfo);
            if (lUserID < 0) {
                std::cout << "Login error, " << NET_DVR_GetLastError() << std::endl;
                NET_DVR_Cleanup();
                return -1;
            } else {
                std::cout << "Login Success" << std::endl;
            }
            std::unique_lock<std::mutex> users_locker(users);
            Users.push_back(lUserID);
            users_locker.unlock();
            NET_DVR_SetExceptionCallBack_V30(0, NULL, ExceptionCallBack, NULL);
            std::cout << "byChanNum:" << struDeviceInfo.byChanNum << std::endl;
            std::cout << "byAlarmOutPortNum:" << struDeviceInfo.byAlarmOutPortNum << std::endl;
            std::cout << "byStartChan:" << struDeviceInfo.byStartChan << std::endl;
            std::cout << "byMainProto:" << struDeviceInfo.byMainProto << std::endl;
            std::cout << "bySubProto:" << struDeviceInfo.bySubProto << std::endl;

            if (struDeviceInfo.byChanNum > 0) {
                auto m_iStartChan = struDeviceInfo.byChanNum;
                auto m_iChanNum = struDeviceInfo.byStartChan;
            } else if (struDeviceInfo.byIPChanNum > 0) {
                auto m_iStartChan = struDeviceInfo.byStartDChan;
                auto m_iChanNum = struDeviceInfo.byIPChanNum + struDeviceInfo.byHighDChanNum * 256;
            }
//            NET_DVR_CLIENTINFO ClientInfo;
//            ClientInfo.lChannel = channel;
//            ClientInfo.hPlayWnd = NULL;
//            ClientInfo.lLinkMode = 0;
//            ClientInfo.byProtoType = 1;
//            ClientInfo.sMultiCastIP = "0.0.0.0";
//
//            LPNET_DVR_PREVIEWINFO lpnetDvrPreviewinfo = new NET_DVR_PREVIEWINFO();
//            lpnetDvrPreviewinfo->lChannel = channel;
//            lpnetDvrPreviewinfo->dwStreamType = stream_type;

            NET_DVR_PREVIEWINFO netDvrPreviewinfo;
            netDvrPreviewinfo.lChannel = channel;
            netDvrPreviewinfo.dwStreamType = stream_type;
            netDvrPreviewinfo.dwLinkMode = 0;
            netDvrPreviewinfo.bBlocked = 0;


            data_struct::CamHandle lRealPlayHandle = NET_DVR_RealPlay_V40(lUserID, &netDvrPreviewinfo,
                                                                          fRealDataCallBack);
//            data_struct::CamHandle lRealPlayHandle = NET_DVR_RealPlay_V30(lUserID, &ClientInfo, fRealDataCallBack, NULL,
//                                                                          TRUE);

            if (lRealPlayHandle < 0) {
                std::cout << "Create Handel error, " << NET_DVR_GetLastError() << std::endl;
                return -1;
            }
            std::unique_lock<std::mutex> handels_locker(handels);
            lRealPlayHandles.push_back(lRealPlayHandle);
            handels_locker.unlock();
            return lRealPlayHandle;
        }

        void CALLBACK HKCamDriver::DecCBFun(long nPort, char *pBuf, long nSize, FRAME_INFO *pFrameInfo, long nReserved1,
                                            long nReserved2) {
            long lFrameType = pFrameInfo->nType;
            static IplImage *pImgYCrCb[MaxCameraNum];
            if (lFrameType == T_YV12) {
                std::unique_lock<std::mutex> locker(read_frame);
//                WaitForSingleObject(cEvent[nPort], INFINITE);
//                ResetEvent(cEvent[nPort]);
                try {
                    if (pImgYCrCb[nPort] == NULL) {
                        pImgYCrCb[nPort] = cvCreateImage(cvSize(pFrameInfo->nWidth, pFrameInfo->nHeight), 8, 3);
                    }
                    if (pImg[nPort] == NULL) {
                        pImg[nPort] = cvCreateImage(
                                cvSize((int) (pFrameInfo->nWidth * Scalefactor),
                                       (int) (pFrameInfo->nHeight * Scalefactor)), 8,
                                3);
                    }

                    yv12toYUV(pImgYCrCb[nPort]->imageData, pBuf, pFrameInfo->nWidth,
                              pFrameInfo->nHeight, pImgYCrCb[nPort]->widthStep);
                    //pFrame[nPort]=cvarrToMat(pImgYCrCb[nPort]).clone();
                    //cv::cvtColor(pFrame[nPort],pFrame[nPort], CV_YCrCb2RGB);
                    //cv::imshow(std::to_string(nPort), pFrame[nPort]);
                    //cv::waitKey(1);
                    cvResize(pImgYCrCb[nPort], pImg[nPort], CV_INTER_LINEAR);
                    cvCvtColor(pImg[nPort], pImg[nPort], CV_YCrCb2RGB);
                    locker.unlock();
                    cond_frame.notify_one();
//                    SetEvent(cEvent[nPort]);
                }
                catch (std::exception &e) {

                }
                catch (cv::Exception &e) {

                }
            }
        }

        void CALLBACK  HKCamDriver::ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser) {
            char tempbuf[256] = {0};
            switch (dwType) {
                case EXCEPTION_RECONNECT:
                    break;
                default:
                    break;
            }
        }

        void HKCamDriver::yv12toYUV(char *outYuv, char *inYv12, int width, int height, int widthStep) {
            int col, row;
            unsigned int Y, U, V;
            int tmp;
            int idx;
            for (row = 0; row < height; row++) {
                idx = row * widthStep;
                int rowptr = row * width;
                for (col = 0; col < width; col++) {
                    tmp = (row / 2) * (width / 2) + (col / 2);
                    Y = (unsigned int) inYv12[row * width + col];
                    U = (unsigned int) inYv12[width * height + width * height / 4 + tmp];
                    V = (unsigned int) inYv12[width * height + tmp];
                    outYuv[idx + col * 3] = Y;
                    outYuv[idx + col * 3 + 1] = U;
                    outYuv[idx + col * 3 + 2] = V;
                }
            }
        }

        void CALLBACK HKCamDriver::fRealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize,
                                                     void *pUser) {
            DWORD dRet;
            DWORD CameraIndex = 0;
            CameraIndex = lRealHandle;
            //printf("lRealHandle = %ld\n", CameraIndex);
            switch (dwDataType) {
                case NET_DVR_SYSHEAD:
                    if (!PlayM4_GetPort(&nPort[CameraIndex]))break;
                    if (dwBufSize > 0) {
                        if (!PlayM4_OpenStream(nPort[CameraIndex], pBuffer, dwBufSize, 1024 * 1024)) {
                            dRet = PlayM4_GetLastError(nPort[CameraIndex]);
                            break;
                        }
                        if (!PlayM4_SetDecCallBack(nPort[CameraIndex], DecCBFun)) {
                            dRet = PlayM4_GetLastError(nPort[CameraIndex]);
                            break;
                        }
                        if (!PlayM4_Play(nPort[CameraIndex], NULL)) {
                            dRet = PlayM4_GetLastError(nPort[CameraIndex]);
                            break;
                        }
                    }
                    break;
                case NET_DVR_STREAMDATA:
                    if (dwBufSize > 0 && nPort[CameraIndex] != -1) {
                        BOOL inData = PlayM4_InputData(nPort[CameraIndex], pBuffer, dwBufSize);
                        while (!inData) {
                            Sleep(10);
                            inData = PlayM4_InputData(nPort[CameraIndex], pBuffer, dwBufSize);
                        }
                    }
                    break;
            }
        }

        cv::Mat HKCamDriver::Read(data_struct::CamHandle lRealPlayHandle) {
            cv::Mat frame(640, 480, CV_8UC3, Scalar(255, 255, 255));;
            int iPort = nPort[lRealPlayHandle];
            if (iPort != -1) {
                std::unique_lock<std::mutex> locker(read_frame);
                cond_frame.wait(locker);
//                WaitForSingleObject(cEvent[iPort], INFINITE);
//                ResetEvent(cEvent[iPort]);
                cv::cvarrToMat(pImg[iPort]).copyTo(frame);
//                SetEvent(cEvent[iPort]);
                locker.unlock();
            }
            return frame;
        }

        data_struct::CamHandle HKCamDriver::Init() {
            return this->InitCamera(this->sIP.c_str(),
                                    this->UsrName.c_str(),
                                    this->PsW.c_str(),
                                    this->Port,
                                    this->channel,
                                    this->stream_type
            );
        }
    }
}